import {
  unref,
  renderSlot,
  resolveDynamicComponent,
  createVNode,
  h,
  mergeProps,
  Fragment,
  isVNode,
  resolveComponent,
  computed,
} from "vue";

import {isObject,isPlainObject,isMap,upperFirst,isArray,clone,merge,has,get,omit} from "lodash-es"

import { IElsElem } from "./inter";

export namespace ElsElemUtil {
  const REG_IS_VUE = /^\s*v-([\w-]+)\s*$/;
  const REG_SLOT = /^\s*(?:(?:v-slot:|#)(\w+)|v-slot)\s*$/;
  const REG_BIND = /^\s*(?:(?:v-bind:|:)(\w+)|v-bind)\s*$/;
  const REG_ON = /^\s*(?:v-on:|@)([\w:]+)\s*$/;
  // 这里面的keys 默认会放入到 root 中，如果希望放入 props 中并且不做过多处理，请使用 contextKeys 来处理~
  const ROOT_KEYS = [
    "tag",
    "setup",
    "hooks",
    "cls",
    "children",
    "contextKeys",
    "excludeKeys",
    "this",
    "slot-scope",
    "slot",
    "render",
    "beforeRender",
    "isDirectRender",
  ];
  
  const HTML_TAGS = [
    "a",
    "abbr",
    "address",
    "area",
    "article",
    "aside",
    "audio",
    "b",
    "base",
    "bdi",
    "bdo",
    "blockquote",
    "body",
    "br",
    "button",
    "canvas",
    "caption",
    "cite",
    "code",
    "col",
    "colgroup",
    "data",
    "datalist",
    "dd",
    "del",
    "details",
    "dfn",
    "dialog",
    "div",
    "dl",
    "dt",
    "em",
    "embed",
    "fieldset",
    "figcaption",
    "figure",
    "footer",
    "form",
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6",
    "head",
    "header",
    "hgroup",
    "hr",
    "html",
    "i",
    "iframe",
    "img",
    "input",
    "ins",
    "kbd",
    "label",
    "legend",
    "li",
    "link",
    "main",
    "map",
    "mark",
    "menu",
    "meta",
    "meter",
    "nav",
    "noscript",
    "object",
    "ol",
    "optgroup",
    "option",
    "output",
    "p",
    "picture",
    "pre",
    "progress",
    "q",
    "rp",
    "rt",
    "ruby",
    "s",
    "samp",
    "script",
    "section",
    "select",
    "slot",
    "small",
    "source",
    "span",
    "strong",
    "style",
    "sub",
    "summary",
    "sup",
    "table",
    "tbody",
    "td",
    "template",
    "textarea",
    "tfoot",
    "th",
    "thead",
    "time",
    "title",
    "tr",
    "track",
    "u",
    "ul",
    "var",
    "video",
    "wbr",
  ];
  export const isHtmlTag = (tag: string): boolean => HTML_TAGS.includes(tag);
  export const getElemAttrs = (
    elem: IElsElem.Elem,
    context: IElsElem.Context
  ) => {
    const keys = Object.keys(elem);
    const res: IElsElem.Attrs = { root: {}, directive: {}, prop: {}, ctx: {} }; // root | directive | props

    keys.forEach((k) => {
      // 需要排除在 props 中的 keys 集合
      const extKeys: string[] = new Array<string>().concat(
        elem.excludeKeys || [],
        context.excludeKeys || []
      );
      if(extKeys.includes(k)){
        return ;
      }
      const v = elem[k];
      const b = REG_IS_VUE.test(k) || REG_BIND.test(k) || REG_ON.test(k);
      // 需要包含在 props 中的keys 集合
      const ctxKeys: string[] = new Array<string>().concat(
        elem.contextKeys || [],
        context.contextKeys || []
      );
      //
      const cb = ctxKeys.includes(k);
      if (REG_SLOT.test(k)) {
        res.root["slot-scope"] = v;
        res.root["slot"] = REG_SLOT.exec(k)?.[1] ?? "default";
      } else if (b) {
        res.directive[k] = v;
      } else if (ROOT_KEYS.includes(k) && !cb) {
        res.root[k] = v;
      } else if (!cb && k == "contextData" && isPlainObject(v)) {
        res.ctx = { ...v };
      } else {
        res.prop[k] = v;
      }
    });
    return res;
  };
  //
  export const getSlotName = (elem: IElsElem.Elem) => {
    const keys = Object.keys(elem).filter((k) => REG_SLOT.test(k));
    if (keys.length > 0) {
      return REG_SLOT.exec(keys[0])?.[1] || "default";
    }
    return "default";
  };

  // 字符串表达式执行 并返回结果
  const ExtKeys = ["this", "true", "false","class"];
  const doEval = (s = "", obj: any = {}, isUnref = false): any => {
    // 剔除不能作为 key 值的 字符串 如 onUpdate:XXX
    const keys = Object.keys(obj)
      .filter((k) => typeof k === "string")
      .filter((k) => !ExtKeys.includes(k))
      .filter((k) => !/[^\w$]/.test(k));
    const vals = keys.map((k) => (isUnref ? unref(obj[k]) : obj[k]));
    const str = "return " + s;
    try {
      return isUnref ? unref(new Function(...keys, str)(...vals)) :new Function(...keys, str)(...vals) ;
    } catch (e) {
      console.error("字符串表达式执行异常！", s, obj, isUnref);
      console.error(keys, vals, str);
      console.error(e);
    }
    return undefined;
  };
  // 指令解析

  const EventKeys = ["pointerevent"];
  export const parseDirective = (
    directive: Record<string, any>,
    scope: Record<string, any>
  ) => {
    const keys = Object.keys(directive);
    const res: Record<string, any> = { "v-bind": {}, "v-on": {} };
    //
    keys.forEach((k) => {
      let v = directive[k];
      if (typeof v === "string") {
        v = doEval(v, scope, true);
      } else {
        v = unref(v);
      }
      // v-bind
      if (REG_BIND.test(k)) {
        // const r = res['v-bind']
        const bk = REG_BIND.exec(k) ?? [];
        //
        if (bk[1] == undefined) {
          if (isMap(v)) {
            for (const [tk, tv] of v) {
              if (REG_IS_VUE.test(tk)) {
                res[tk] = tv;
              } else {
                res["v-bind"][tk] = tv;
              }
            }
          } else if (isObject(v)) {
            Object.keys(v).forEach((tk) => {
              if (REG_IS_VUE.test(tk)) {
                res[tk] = v[tk];
              } else {
                res["v-bind"][tk] = v[tk];
              }
            });
          }
        }
        //
        else {
          res["v-bind"][bk[1]] = v;
        }
      }
      // v-on
      else if (REG_ON.test(k)) {
        const ek = REG_ON.exec(k) ?? [];
        if (ek[1]) {
          const tk = "on" + upperFirst(ek[1]);
          res["v-on"][tk] =
            typeof v === "function"
              ? (e: Event, ...args: any[]) => {
                  const t = Object.prototype.toString
                    .call(e)
                    .split(" ")[1]
                    .replace("]", "")
                    .toLowerCase();
                  const params = { ...scope };
                  if (EventKeys.includes(t)) {
                    params.$event = e;
                  } else {
                    args = [e, ...args];
                  }
                  if (args.length > 0) {
                    params.$ = args;
                  }
                  return v.apply(scope, [params]);
                }
              : v;
        }
      }
      //
      else {
        if(k === 'v-if'){
          if (typeof v === "function") {
            res[k] = computed(() => v(scope)).value;
          }else{
            res[k] = computed(() => v).value
          }
        }else{
          res[k] = v;
        }
      }
    });
    return res;
  };

  // 循环匹配并执行回调
  const loopExec = (
    reg: RegExp,
    str: string,
    call: (res: RegExpExecArray) => void
  ) => {
    let t: RegExpExecArray | null;
    while ((t = reg.exec(str)) !== null) {
      call(t);
    }
  };

  // 字符串模板 表达式解析 aa {{ a+b }} bb {{ c + d }}
  const REG_EXP = /\{\{\s*(.*?)\s*\}\}/g;
  export const getExpValue = (str: string, scope: Record<string, any> = {}) => {
    if (!str) {
      return "";
    }
    let result:any[] = [];
    let res = str;
    loopExec(REG_EXP, str, ([s, exp]) => {
      try {
        const v = doEval(exp, scope, true);
        if(isVNode(v) || Array.isArray(v)){
          result = result.concat(v)
        }else{
          res = res.replace(s, v);
        }
      } catch (e) {
        console.error("=====字符串模板解析异常！=====", exp, scope);
      }
    });
    if(res != str || result.length == 0){
      result.push(res)
    }
    return result;
  };

  // 解构赋值方法 支持不完全对象解构赋值 {k:key,k,k:key} /^\s?{.*}\s?$/ [a,b,c] = [0,1,2]
  const REG_ISARRAY = /^\s*\[(.*)\]\s*$/;
  const REG_ISOBJECT = /^\s*\{(.*)\}\s*$/;
  export const getDestruct = (
    str: string | boolean,
    scope: any = {}
  ): Record<string, any> => {
    const s = unref(scope);
    if (!str || typeof str === "boolean") {
      return str === false ? {} : s;
    }
    const res: any = {};
    if (isArray(s) && REG_ISARRAY.test(str)) {
      const as = REG_ISARRAY.exec(str);
      const ks = as?.[1] ? as[1].split(",").map((k) => k.trim()) : [];
      ks.forEach((k, i) => {
        res[k] = s?.[i];
      });
    } else if (isObject(s) && REG_ISOBJECT.test(str)) {
      const as = REG_ISOBJECT.exec(str);
      const ks = as?.[1] ? as[1].split(",").map((k) => k.trim()) : [];
      ks.forEach((k) => {
        const [sk, tk] = k.split(":").map((s) => s.trim());
        res[tk || sk] = get(s, sk);
      });
    } else {
      res[str] = s;
    }
    return res;
  };

  // 特殊元素的渲染
  export const specialRender: Record<string, IElsElem.RenderFunction> = {
    template({ children }) {
      // @ts-ignore
      return children?.default();
    },
    slot({ props, children, slots }) {
      const name = props.name || "default";
      delete props.name;
      // console.log(this.$_attrs)
      const childs = children?.[name] ?? (() => []);
      // @ts-ignore
      return renderSlot(slots, name, props, () => childs());
    },
    component({ props, children }) {
      const name: string = props.is;
      delete props.is;
      const c: any = resolveDynamicComponent(name);
      return createVNode(c, props, children);
    },
  };

  // 默认元素渲染
  export const defaultRender: IElsElem.RenderFunction = (config) => {
    const tagname = config.tagname;
    // 特殊元素的默认渲染函数
    if (specialRender[tagname]) {
      return specialRender[tagname](config);
    }
    //
    const directives = config.directives;
    const children = config.children;
    const tag = typeof config.tag === 'string' ? config.isHtml ? config.tag : resolveComponent(config.tag) : config.tag;
    const props = config.props;
    // v-text:: 已知当有 v-text 属性时，会覆盖掉内部的 children 属性
    if (directives["v-text"]) {
      // @ts-ignore
      // return h(Fragment,null,h(tag, props, directives["v-text"] as string));
      return h(tag, props, directives["v-text"] as string)
    }
    // 返回 VNode
    // @ts-ignore
    // return h(Fragment,null,h(tag, props, children));
    return h(tag, props, children);
  };

  // props 过滤
  export const propsFilter = (
    props: Record<string, any>,
    extKeys: string[]
  ) => {
    return omit(props, extKeys);
  };

  //
  export const getProps = (elem: IElsElem.Elem, ctx: Record<string, any>) => {
    const { prop, directive } = getElemAttrs(elem, {});
    //
    const dt = parseDirective(directive, ctx);
    //
    return mergeProps(prop, dt["v-bind"], dt["v-on"]);
  };
}

// console.log(new Date().format("yyyy"))
// 数据工具类
export namespace DataUtils {
  //
  export const dateFormat = (date: Date, template = "yyyy-MM-dd") => {};
  // 获取数据中的属性 支持多个属性字段，
  export type IGetDataAttrKey = TOA<string> | ((d: any) => any);
  export interface IGetDataAttrOptions {
    multiple?: boolean;
    notValues?: any[];
    value?: any;
  }
  export const getDataAttr = <T = Record<any, any>>(
    data: T,
    key: IGetDataAttrKey,
    ops?: IGetDataAttrOptions
  ) => {
    // 判定是否是函数
    if (typeof key == "function") {
      return key(data);
    }
    // 设置选项
    const {
      multiple = false,
      notValues = [undefined, null, ""],
      value,
    } = ops || {};
    if (typeof key == "string") {
      const v = get(data, key);
      if (notValues?.includes(v) && value !== undefined) {
        return value;
      }
      return v;
    }
    const res: any[] = [];
    key.map((k) => {
      const v = get(data, k);
      if (!notValues?.includes(v)) {
        res.push(v);
      }
    });
    if (value != undefined && res.length == 0) {
      res.push(value);
    }
    //
    return multiple ? res : res[0];
  };
  //
  export const getSetGroupData = <T = Record<any, any>>(
    data: T[],
    group: any
  ) => {};
  // 集合分组
  export type ISetGroupItem = string | Record<string, any> | ISetGroupItem[];
  export type ISetGroupStruct = [string, ...ISetGroupItem[]];
  export type ISetGroupData = [id?: ISetGroupStruct, ...rest: TOA<string>[]];
  export interface ISetGroup<T = Record<any, any>> {
    data?: T[];
    group?: ISetGroupData;
    keys?: IGetDataAttrKey;
    groupAttr?:
      | Record<any, any>
      | ((attr: ISetGroupItem[]) => Record<any, any>);
  }

  export class SetGroup<T extends Record<any, any> = Record<any, any>> {
    //
    private data: T[] = [];
    private group: ISetGroupData = [];
    private keys: IGetDataAttrKey = ["id", "prop"];
    private groupAttr:
      | Record<any, any>
      | ((attr?: ISetGroupItem[]) => Record<any, any>) = {};
    //
    private source: T[] = [];
    private list: T[] = [];
    private listKeys: any[] = [];
    //
    private groupTemp: Record<any, any> = {};
    //
    constructor(params: ISetGroup = {}) {
      Object.assign(this, params);
      //
    }
    // clone
    public clone() {}
    // 增加数据
    public addData(data: T[]) {
      this.source = new Array().concat(this.data, data);
      return this;
    }
    //
    public mergeData(attr: Record<any, any> = {}) {
      this.list = this.source.map((d) =>
        clone(merge({}, d, mergeProps(d, attr)))
      );
      this.resetDataKeys();
      return this;
    }
    public resetData(fun: (d: T) => T) {
      this.list = this.source.map((d) => clone(fun(d)));
      this.resetDataKeys();
      return this;
    }
    public resetDataKeys(keys?: IGetDataAttrKey) {
      if (!keys) {
        keys = this.keys;
      }
      if (keys != undefined && this.list.length > 0) {
        this.listKeys = this.list.map((d) =>
          getDataAttr(d, keys as IGetDataAttrKey)
        );
      }
      return this;
    }
    public checkKeys() {
      if (new Set(this.listKeys).size < this.listKeys.length) {
        return false;
      }
      return true;
    }
    //
    public getGroup(group?: ISetGroupData) {
      const gp = group || this.group || [];
      // 是否包含结构体数据
      const [v0, ...gs] = gp;
      const bool =
        Array.isArray(v0) && typeof v0[0] === "string" && /^:/.test(v0[0]);
      const tgs = bool ? gs : gp;
      const temp = this.getGroupItem(tgs as TOA<string>[], bool);
      // 结构体解析
      if (bool) {
      } else {
        return temp;
      }
    }
    // 分组结构
    public getGroupStruct(struct: ISetGroupItem[],temp:Record<string,T[]>) {
      const def =
        typeof this.groupAttr === "function"
          ? this.groupAttr(struct)
          : this.groupAttr;
      const children: T[] = [];
      const l = struct.length;
      for (let i = 0; i < l; i++) {
        const d = struct[i];
        // 判定首位是否是 class 参数
        if (i == 0 && typeof d === "string" && /^:/.test(d)) {
          //
          continue;
        }
        // 是否是对像
        if (isPlainObject(d) && (d as any).id) {
          if(has(temp,(d as any).id)){
            children.push(merge({},def,mergeProps(def,d as any),{children:get(temp,(d as any).id)}))
          }
          continue;
        }
        // 是否是字符串 ID
        if (typeof d === "string") {
          // if(/^@/.test(d)){
          //   const keys = /^@(.*)/.exec(d)?.[1].split(',')
          //   keys && children.push(...keys.map((d) => this.getListDataByKey(d)).filter(d => d != undefined) as T[])
          // }
          // else if(has(this.layoutTempData,d)){
          //   children.push(merge({},def,{children:get(this.layoutTempData,d)}))
          // }
          continue;
        }
        // 是否需要递归处理
        if (Array.isArray(d)) {
          children.push(this.getGroupStruct(d,temp))
          continue;
        }
      }

      return {} as T
    }
    public getGroupItem(groupItems: TOA<string>[], hasStruct: boolean = false) {
      const res: Record<string, T[]> = { $: [] };
      const rd: T[] = [];
      groupItems.map((d) => {
        if (typeof d === "string") {
          const t = this.getListData(d);
          t && res.$.push(t) && rd.push(t);
        } else if (Array.isArray(d)) {
          const [v0, ...ks] = d;
          const ts = /^@/.test(v0) ? ks : d;
          const id = (/^@(\w+)?/.exec(v0) || [])[1] || "$";
          if (!has(res, id)) {
            res[id] = [];
          }
          const td = ts
            .map((k) => this.getListData(k))
            .filter((d) => d != undefined) as T[];
          res[id].push(...td);
          rd.push(this.getGroupAttr({ children: td }) as any);
        }
      });
      return hasStruct ? res : rd;
    }
    private getGroupAttr(
      prop: Record<any, any> = {},
      item: ISetGroupItem[] = []
    ) {
      const attr =
        typeof this.groupAttr == "function"
          ? this.groupAttr(item)
          : this.groupAttr;
      return merge({}, attr, mergeProps(attr, prop));
    }
    private getListData(key: string) {
      const ins = this.listKeys.indexOf(key);
      if (ins > -1) {
        return this.list[ins];
      }
      return undefined;
    }
  }
}


export const isComponent = (obj:any) => {
  const bool = obj && typeof obj == 'object' && obj?.tag == undefined && (obj.setup != undefined || obj.render != undefined) && (obj.name || obj.__name)
  return bool;
}